import os
from pathlib import Path
from typing import Sequence

from fastapi import UploadFile, status
from fastapi.responses import FileResponse, StreamingResponse

from app.config import settings
from app.core.custom_exception import CustomException
from app.crud.base_crud import BaseCRUD
from app.models.base import IdType
from app.models.file import File
from app.schemas.file_schema import FileCreate, FileUpdate
from app.utils.file import format_file_size, get_file_md5, get_file_path, create_zip


class FileCRUD(BaseCRUD[File, FileCreate, FileUpdate]):

    async def _save_file_to_disk(self, file: UploadFile, file_path: str):
        """
        保存文件到磁盘
        :param file: 上传的文件
        :param file_path: 文件路径
        :return:
        """
        local_path = os.path.join(settings.WEB_ROOT, file_path)
        try:
            os.makedirs(os.path.dirname(local_path), exist_ok=True)
            with open(local_path, "wb") as f:
                while chunk := file.file.read(1024 * 1024):
                    f.write(chunk)
        except OSError as e:
            raise CustomException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, message=f"保存文件失败: {str(e)}")


    async def _create_file_record( self, file: UploadFile, md5: str, file_dir: str ) -> File:
        """
        创建文件记录
        :param file: 上传的文件
        :param md5: 文件md5
        :param file_dir: 所在文件夹
        :return: 文件模型
        """
        extension = Path(file.filename).suffix.lower()
        file_path = os.path.join(file_dir, f"{md5}{extension}")

        await self._save_file_to_disk(file, file_path)

        size_format = format_file_size(file.size)
        file_data = FileCreate(
            name=Path(file.filename).stem,
            save_name=md5,
            extension=extension,
            type=file.content_type,
            size=file.size,
            size_format=size_format,
            md5=md5,
            url=file_path,
            dir=file_dir,
        )

        db_obj = self.model.model_validate(file_data)
        return await self.context.update(entity=db_obj)

    async def _update_file_record(self, db_file: File, file: UploadFile, md5: str) -> File:
        """
        更新文件记录
        :param db_file: 文件模型
        :param file: 上传的文件
        :param md5: 文件MD5
        :return: 文件模型
        """
        extension = Path(file.filename).suffix.lower()
        new_data = FileUpdate(
            md5=md5,
            name=Path(file.filename).stem,
            extension=extension,
            type=file.content_type,
            is_delete=False,
            id=db_file.id
        )
        update_data = new_data.model_dump(exclude_unset=True)
        db_file.sqlmodel_update(update_data)
        return await self.context.update( entity=db_file)

    async def upload_file(self, file: UploadFile,file_dir: str = None) -> File:
        """
        上传单个文件
        :param file: 上传的文件
        :param file_dir: 所在文件夹
        :return: 文件模型
        """
        file_dir = file_dir or settings.DEFAULT_UPLOAD_FILE_DIR

        if file.size > settings.MAX_FILE_SIZE:
            raise CustomException(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, message=f"文件大小超过 {settings.MAX_FILE_SIZE} 字节的最大限制")

        md5 = get_file_md5(file)

        result = await self.context.where(self.model.md5 == md5).first()

        if result:
            return await self._update_file_record(result, file, md5)
        else:
            return await self._create_file_record(file, md5, file_dir)

    async def upload_files(self, files: Sequence[UploadFile],file_dir: str = None) -> Sequence[File]:
        """
        批量上传文件
        :param files: 上传文件列表
        :param file_dir: 所在文件夹
        :return: 文件模型列表
        """
        file_dir = file_dir or settings.DEFAULT_UPLOAD_FILE_DIR
        result_files = []

        # 验证所有文件大小
        for file in files:
            if file.size >  settings.MAX_FILE_SIZE:
                raise CustomException(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
                                      message=f"文件 {file.filename} 大小超过 {settings.MAX_FILE_SIZE} 字节的最大限制")

        # 获取所有文件的md5
        md5s = [get_file_md5(file) for file in files]

        # 获取所有已经存在的文件
        db_files = await self.context.where(self.model.md5.in_(md5s)).to_list()

        # 创建MD5到文件的映射
        md5_to_file = {get_file_md5(f): f for f in files}
        md5_to_db_file = {f.md5: f for f in db_files}

        # 处理已存在和新增的文件
        for md5 in md5s:
            file = md5_to_file[md5]
            if md5 in md5_to_db_file:
                updated_file = await self._update_file_record( md5_to_db_file[md5], file, md5)
                result_files.append(updated_file)
            else:
                new_file = await self._create_file_record( file, md5, file_dir)
                result_files.append(new_file)

        return result_files

    async def download_file(self, id: IdType) -> FileResponse:
        db_file = await self.validate_by_id(id=id)

        file_path = get_file_path(url=db_file.url)

        return FileResponse(
            file_path,
            media_type=db_file.type,
            filename=db_file.name + db_file.extension,
        )

    async def download_files(self, ids: Sequence[IdType]) -> StreamingResponse:
        # 1. 从数据库获取文件信息
        db_files = await self.validate_by_ids(ids=ids)
        # 创建内存ZIP
        zip_file_buffer = create_zip(files=list(db_files))
        # 返回流式响应
        return StreamingResponse(
            zip_file_buffer,
            media_type="application/zip",
            headers={
                "Content-Disposition": "attachment; filename=download.zip",
                "Content-Type": "application/zip",
            },
        )

